import pygame
import sys
import numpy as np
import player
import PlayerBullet
import Buff
import Trap
import random
import enemy
import EnemyStar
import room
import map
import map2
import map3
import backpack
import math

# Special parameters initialization
fps = 600  # fps threshold
fps_lock = pygame.time.Clock()
player_base_speed = 5  # player's base speed
decrease_scale = 1  # speed decreasing scale(potion and magic)
increase_scale = 1  # speed increasing scale(speed boots)
trap_damage = 200  # explode trap damage
freeze_trap_time = 200  # freeze time
bullet_damage = 30  # player's bullet damage
difficulty = 0  # hardship parameter

# load basic resource
pygame.init()
pygame.mixer.init()
pygame.display.set_caption("Tomb Raider")
icon_image = pygame.image.load(r"image/icon.JPG")
pygame.display.set_icon(icon_image)
ui_back = pygame.image.load("image/uiback.png")
load_image = pygame.image.load("image/loading.png")
ui_back_1 = pygame.image.load("image/subui_bac.png")

# resolution settings
resolution = 752, 700
screen = pygame.display.set_mode(resolution)


# game main function, implement all game elements
def main():
    pygame.mixer.init()

    # functions that used to generate main menu, player may choose game difficulty or exit the game
    def show_ui():
        hardship_ = 0  # initialize difficulty parameter
        start_ck = pygame.Surface(screen.get_size())  # main menu surface
        start_ck2 = pygame.Surface(screen.get_size())  # loading image surface
        start_ck = start_ck.convert()
        start_ck2 = start_ck2.convert()
        start_ck.fill((255, 255, 255))
        start_ck2.fill((0, 255, 0))

        # load soundtrack and image resources
        sound = pygame.mixer.Sound("sound/soundtrack/ui_back.mp3")
        sound.set_volume(1)
        sound.play(-1)

        # load image resources and enable alpha channel for png files
        i1 = pygame.image.load("./image/h1.png")
        i1.convert_alpha()
        i11 = pygame.image.load("./image/h2.png")
        i11.convert_alpha()

        i2 = pygame.image.load("./image/m1.png")
        i2.convert_alpha()
        i21 = pygame.image.load("./image/m2.png")
        i21.convert_alpha()

        i3 = pygame.image.load("./image/d1.png")
        i3.convert_alpha()
        i31 = pygame.image.load("./image/d2.png")
        i31.convert_alpha()

        i4 = pygame.image.load("./image/n1.png")
        i4.convert_alpha()
        i41 = pygame.image.load("./image/n2.png")
        i41.convert_alpha()

        #  examine the mouse pointer behavior
        n1 = True
        while n1:
            fps_lock.tick(30)
            buttons = pygame.mouse.get_pressed()
            x1, y1 = pygame.mouse.get_pos()
            start_ck.blit(ui_back, (0, 0))
            if 550 <= x1 <= 750 and 400 <= y1 <= 450:
                start_ck.blit(i11, (550, 400))
                if buttons[0]:
                    n1 = False
                    hardship_ = 1  # difficulty is set to be easy

            elif 550 <= x1 <= 750 and 450 <= y1 <= 500:
                start_ck.blit(i21, (550, 450))
                if buttons[0]:
                    n1 = False
                    hardship_ = 2  # difficulty is set to be expert

            elif 750 >= x1 >= 550 >= y1 >= 500:
                start_ck.blit(i31, (550, 500))
                if buttons[0]:
                    n1 = False
                    hardship_ = 3  # difficulty is set to be nightmare

            elif 550 <= x1 <= 750 and 550 <= y1 <= 600:
                start_ck.blit(i41, (550, 550))
                if buttons[0]:
                    pygame.quit()
                    exit()  # exit

            else:
                start_ck.blit(i1, (550, 400))
                start_ck.blit(i2, (550, 450))
                start_ck.blit(i3, (550, 500))
                start_ck.blit(i4, (550, 550))

            screen.blit(start_ck, (0, 0))
            pygame.display.update()

            # examine if exit button in program box is clicked
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    exit()

        # show the processed surface on the screen
        start_ck2.blit(load_image, (0, 0))
        screen.blit(start_ck2, (0, 0))
        pygame.display.update()
        sound.stop()
        return hardship_

    # call main menu generation function
    hardship = show_ui()

    # load shadow for nightmare mode
    shadow = pygame.image.load(r"image/shadow_invalid.png")
    if hardship == 3:
        shadow = pygame.image.load(r"image/shadow.png")

    # function that used to generate sub-menu when player is dead in the game
    # player may choose to back to the main menu to start over
    def show_sub_ui():
        pygame.mixer.init()
        sound = pygame.mixer.Sound("sound/soundtrack/ruins.ogg")
        sound.set_volume(0.7)
        sound.play(-1)
        start_ck = pygame.Surface(screen.get_size())
        start_ck2 = pygame.Surface(screen.get_size())
        start_ck = start_ck.convert()
        start_ck2 = start_ck2.convert()
        start_ck.fill((255, 255, 255))
        start_ck2.fill((0, 255, 0))

        i1 = pygame.image.load("./image/ui1.png")
        i1.convert_alpha()
        i11 = pygame.image.load("./image/ui2.png")
        i11.convert_alpha()

        n1 = True
        while n1:
            fps_lock.tick(30)
            buttons = pygame.mouse.get_pressed()
            x1, y1 = pygame.mouse.get_pos()
            start_ck.blit(ui_back_1, (0, 0))
            if 175 <= x1 <= 575 and 250 <= y1 <= 350:
                start_ck.blit(i11, (175, 250))
                if buttons[0]:
                    n1 = False

            else:
                start_ck.blit(i1, (175, 250))

            screen.blit(start_ck, (0, 0))
            pygame.display.update()

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    exit()

        start_ck2.blit(load_image, (0, 0))
        screen.blit(start_ck2, (0, 0))
        pygame.display.update()
        sound.stop()
        main()

    laura = player.Player()

    # load room background
    back_img = pygame.image.load(r"image/background.png")

    # load rooms layout for easy mode
    if hardship == 1:
        MAP \
            = [map.Map0(), map.Map1(), map.Map2(), map.Map3(), map.Map4(), map.Map5(), map.Map6(), map.Map7(),
               map.Map8(), map.Map9(), map.Map10(), map.Map11(), map.Map12(), map.Map13(), map.Map14(), map.Map15(),
               map.Map16(), map.Map17(), map.Map18(), map.Map19(), map.Map20(), map.Map21(), map.Map22(), map.Map23(),
               map.Map24(), map.Map25(), map.Map26(), map.Map27(), map.Map28()]
        difficulty = 4

    # load rooms layout for nightmare mode
    elif hardship == 3:
        MAP \
            = [map2.Map0(), map2.Map1(), map2.Map2(), map2.Map3(), map2.Map4(), map2.Map5(), map2.Map6(), map2.Map7(),
               map2.Map8(), map2.Map9(), map2.Map10(), map2.Map11(), map2.Map12(), map2.Map13(), map2.Map14(),
               map2.Map15(),
               map2.Map16(), map2.Map17(), map2.Map18(), map2.Map19(), map2.Map20(), map2.Map21(), map2.Map22(),
               map2.Map23(),
               map2.Map24(), map2.Map25(), map2.Map26(), map2.Map27(), map2.Map28()]
        difficulty = 6

    # load rooms layout for expert mode
    elif hardship == 2:
        MAP \
            = [map3.Map0(), map3.Map1(), map3.Map2(), map3.Map3(), map3.Map4(), map3.Map5(), map3.Map6(), map3.Map7(),
               map3.Map8(), map3.Map9(), map3.Map10(), map3.Map11(), map3.Map12(), map3.Map13(), map3.Map14(),
               map3.Map15(),
               map3.Map16(), map3.Map17(), map3.Map18(), map3.Map19(), map3.Map20(), map3.Map21(), map3.Map22(),
               map3.Map23(),
               map3.Map24(), map3.Map25(), map3.Map26(), map3.Map27(), map3.Map28()]
        difficulty = 8

    wall = MAP[25]
    Small_Map = MAP[28]

    # initialize current chamber
    enemy_number = wall.enemy_number
    wall.play_back()  # play background soundtrack
    matrix = wall.maze
    graph = wall.block_maze
    laura_attack = list()  # initialize Laura's melee attack container
    laura_bullets = list()  # initialize Laura's bullet container
    steady_buff = wall.steady_buff  # initialize chamber props container
    traps = wall.traps  # load remaining traps in the chamber
    enemies = wall.enemies  # load remaining enemies in the chamber
    guard_bullets = list()  # initialize skeleton guards' magic attack container
    bags = list()  # initialize the bag container

    # load the corresponding resource of Laura's backpack
    bags.append(backpack.LauraBag(0, 630))
    bags.append(backpack.LauraBag(70, 630))
    bags.append(backpack.LauraBag(140, 630))
    bags.append(backpack.LauraBag(210, 630))
    bags.append(backpack.LauraBag(280, 630))

    # load Laura's state icon images
    freeze_icon = pygame.image.load(r"image/icon_buff_freeze.png")
    fast_icon = pygame.image.load(r"image/icon_buff_fast.png")
    slow_icon = pygame.image.load(r"image/icon_buff_slow.png")

    # pack current room resources
    curr_room = room.Room(matrix, laura_attack, laura_bullets, steady_buff,
                          traps, enemies, guard_bullets, back_img, enemy_number, graph)

    # path finding function based on A* algorithm
    # movable enemies may approach Laura as quickly as possible
    def find_path(gra, a_star_graph, source, target):
        source_x = math.floor((source[0] - 3) / 24)
        source_y = math.floor((source[1] - 3) / 24)
        target_x = math.floor((target[0] - 3) / 24)
        target_y = math.floor((target[1] - 3) / 24)
        if target_y < 0:
            target_y = 0
        str_source = (source_y, source_x)
        str_target = (target_y, target_x)
        openL = list()
        CloseL = list()
        openL.append(str_source)
        while openL:
            curr = (a_star_graph[openL[0][0]][openL[0][1]].coord_x, a_star_graph[openL[0][0]][openL[0][1]].coord_y)
            for i in openL:
                if a_star_graph[i[0]][i[1]].F < a_star_graph[curr[0]][curr[1]].F:
                    curr = i
            openL.remove(curr)
            CloseL.append(curr)
            surroundings = list()
            x1 = curr[0] - 1
            x2 = curr[0] + 1
            y1 = curr[1] - 1
            y2 = curr[1] + 1
            if 0 <= x1 < 25:
                if gra[x1][curr[1]] == 0 and ((x1, curr[1]) not in CloseL):
                    surroundings.append((x1, curr[1]))
            if 0 <= x2 < 25:
                if gra[x2][curr[1]] == 0 and ((x2, curr[1]) not in CloseL):
                    surroundings.append((x2, curr[1]))
            if 0 <= y1 < 25:
                if gra[curr[0]][y1] == 0 and ((curr[0], y1) not in CloseL):
                    surroundings.append((curr[0], y1))
            if 0 <= y2 < 25:
                if gra[curr[0]][y2] == 0 and ((curr[0], y2) not in CloseL):
                    surroundings.append((curr[0], y2))
            for temp in surroundings:
                if temp not in openL:
                    a_star_graph[temp[0]][temp[1]].G = a_star_graph[curr[0]][curr[1]].G + 1
                    a_star_graph[temp[0]][temp[1]].H = math.sqrt(
                        pow((temp[0] - str_target[0]), 2) + pow((temp[1] - str_target[1]) ^ 2, 2))
                    a_star_graph[temp[0]][temp[1]].F = a_star_graph[temp[0]][temp[1]].G + a_star_graph[temp[0]][
                        temp[1]].H
                    a_star_graph[temp[0]][temp[1]].prev = [curr[0], curr[1]]
                    openL.append(temp)
                else:
                    temp_G = a_star_graph[curr[0]][curr[1]].G + 1
                    if temp_G < a_star_graph[temp[0]][temp[1]].G:
                        a_star_graph[temp[0]][temp[1]].G = temp_G
                        a_star_graph[temp[0]][temp[1]].F = a_star_graph[temp[0]][temp[1]].G + a_star_graph[temp[0]][
                            temp[1]].H
                        a_star_graph[temp[0]][temp[1]].prev = [curr[0], curr[1]]

                if str_target in CloseL:
                    return str_target
        return [-1, -1]

    def get_path(a_star_graph, source, target):
        curr = [-1, -1]
        curr[0] = a_star_graph[target[0]][target[1]].prev[0]
        curr[1] = a_star_graph[target[0]][target[1]].prev[1]
        source_x = math.floor((source[0] - 3) / 24)
        source_y = math.floor((source[1] - 3) / 24)
        str_source = (source_y, source_x)
        path = list()
        path.append((target[0], target[1]))
        while curr[0] != -1 and curr[1] != -1:
            path.insert(0, (curr[0], curr[1]))
            curr = a_star_graph[curr[0]][curr[1]].prev
        if path[0][1] != source_x and path[0][0] != source_y:
            path = list()
            path.append(str_source)
        #
        return path

    # function used to generate main game screen
    def show_map():
        # pass nonlocal variable to the function
        nonlocal curr_room
        nonlocal wall
        nonlocal laura

        running = True
        while running:
            if not laura.alive:
                pygame.mixer.quit()
                show_sub_ui()  # if Laura is dead, generate sub menu

            # speed boot and poison interaction
            # frame-based timer to make Laura increase or decrease speed for a period of time

            curr_speed = player_base_speed
            # acceleration
            if laura.remain_buff > 0:
                curr_speed = curr_speed + increase_scale
                laura.remain_buff -= 1
            # deceleration
            if laura.remain_de_buff > 0:
                curr_speed = curr_speed - decrease_scale
                laura.remain_de_buff -= 1
            # frozen and player is unable to move
            if laura.freeze_time > 0:
                curr_speed = 0
                laura.freeze_time -= 1
            screen.blit(curr_room.back_image, (0, 0))

            for each in wall.blockGroup:
                screen.blit(each.image, each.rect)

            for each in wall.doorGroup:
                screen.blit(each.image, each.rect)

            # keyboard interaction
            for event in pygame.event.get():
                if event.type == pygame.QUIT:  # close program box
                    pygame.quit()
                    exit()
                if event.type == pygame.KEYDOWN:  # find keyboard pressed
                    if event.key == pygame.K_ESCAPE:
                        pygame.quit()
                        sys.exit()
                    # space pressed: Laura shooting
                    if event.key == pygame.K_SPACE:
                        if laura.remain_bullet == 0:
                            sound = pygame.mixer.Sound("sound/sound_effect/no_bullet.mp3")
                            sound.set_volume(0.4)
                            sound.play()
                        else:
                            laura.remain_bullet -= 1
                            curr_bullet = PlayerBullet.LauraBullet(laura)
                            curr_room.laura_bullets.append(curr_bullet)
                            sound = pygame.mixer.Sound("sound/sound_effect/gun_fire.mp3")
                            sound.set_volume(0.2)
                            sound.play()
                    # z pressed: Laura melee attack
                    if event.key == pygame.K_z:
                        laura.is_flash = True
                        sound = pygame.mixer.Sound("sound/sound_effect/laura_melee.wav")
                        sound.set_volume(1)
                        sound.play()
                    # esc pressed: exit game
                    if event.key == pygame.K_ESCAPE:
                        pygame.quit()
                        sys.exit()
                    # s pressed: suicide
                    if event.key == pygame.K_s:
                        laura.HP = 0
                        laura.alive = False

            key_list = pygame.key.get_pressed()

            # Laura's melee attack disables keyboard input
            if not laura.is_flash:
                laura.flag = False
                if key_list[pygame.K_UP]:
                    laura.player_speed_y = - curr_speed
                    laura.player_face = 0
                    laura.flag = True

                elif key_list[pygame.K_DOWN]:
                    laura.player_speed_y = curr_speed
                    laura.player_face = 1
                    laura.flag = True
                else:
                    laura.player_speed_y = 0

                if key_list[pygame.K_LEFT]:
                    laura.player_speed_x = - curr_speed
                    laura.player_face = 3
                    laura.flag = True

                elif key_list[pygame.K_RIGHT]:
                    laura.player_speed_x = curr_speed
                    laura.player_face = 2
                    laura.flag = True
                else:
                    laura.player_speed_x = 0

                if laura.flag:
                    laura.is_walk = True
                else:
                    laura.is_walk = False

            # use generated parameters to move Laura
            laura.move_player(curr_room)

            # show_attack method
            # implement Laura's melee attack
            def show_attack():
                # pass non-local parameter
                nonlocal wall
                for b in curr_room.laura_attack:
                    screen.blit(b.image, (b.coord_x, b.coord_y))
                    if b.dir == 0:
                        b.coord_y -= b.speed
                    elif b.dir == 1:
                        b.coord_y += b.speed
                    elif b.dir == 2:
                        b.coord_x += b.speed
                    else:
                        b.coord_x -= b.speed

                    # if player in the range of damage, enemy lose its HP
                    is_break = False
                    for curr_enemy in curr_room.enemies:
                        if not curr_enemy.state:
                            if pow((curr_enemy.coord_x - b.coord_x), 2) + pow((curr_enemy.coord_y - b.coord_y),
                                                                              2) <= 900:
                                curr_enemy.damage_enemy(bullet_damage)
                                if curr_enemy.state:
                                    wall.enemy_number -= 1
                                    curr_room.enemy_number = wall.enemy_number
                                curr_enemy.death_()
                                curr_room.laura_attack.remove(b)
                                is_break = True
                                break
                    if is_break:
                        continue
                    if abs(laura.coord_x - b.coord_x) > 20 or abs(laura.coord_y - b.coord_y) > 20:
                        curr_room.laura_attack.remove(b)

            show_attack()

            # show_bullet method
            # implement interaction of Laura's bullet with wall and enemy entities
            def show_bullet():
                for b in curr_room.laura_bullets:
                    screen.blit(b.image, (b.coord_x, b.coord_y))
                    if b.dir == 0:
                        b.coord_y -= b.speed
                    elif b.dir == 1:
                        b.coord_y += b.speed
                    elif b.dir == 2:
                        b.coord_x += b.speed
                    else:
                        b.coord_x -= b.speed
                    # bullet damage enemy if it collide with enemy
                    isbreak = False
                    for curr_enemy in curr_room.enemies:
                        if not curr_enemy.state:
                            if pow((curr_enemy.coord_x + 25 - b.coord_x), 2) + pow(
                                    (curr_enemy.coord_y + 25 - b.coord_y),
                                    2) <= 900:
                                curr_enemy.damage_enemy(bullet_damage)
                                if curr_enemy.state:
                                    wall.enemy_number -= 1
                                    curr_room.enemy_number = wall.enemy_number
                                curr_enemy.death_()
                                sound_1 = pygame.mixer.Sound("sound/sound_effect/bullet_on_enemy.wav")
                                sound_1.set_volume(0.7)
                                sound_1.play()
                                curr_room.laura_bullets.remove(b)
                                isbreak = True
                                break
                    if isbreak:
                        continue
                    # bullet will be removed if it fly out of the chamber
                    if b.coord_x < 10 or b.coord_x > 620:
                        curr_room.laura_bullets.remove(b)
                        continue
                    elif b.coord_y < 10 or b.coord_y > 620:
                        curr_room.laura_bullets.remove(b)
                        continue
                    if curr_room.state(b.coord_x, b.coord_y) == 1:
                        curr_room.laura_bullets.remove(b)
                        sound_2 = pygame.mixer.Sound("sound/sound_effect/bullet_on_wall.wav")
                        sound_2.set_volume(0.5)
                        sound_2.play()
                        continue

            show_bullet()

            # judge whether Laura may pass the chamber door or not
            def judge_over():
                if laura.remain_bar == 1:
                    wall.over = True
                elif wall.enemy_number == 0:
                    wall.over = True
                else:
                    wall.over = False

            judge_over()

            # shift chambers
            # shift current chamber resource(already stored) and load the target room resource
            # array based undirected graph implementation applied
            if wall.over:
                for i in range(28):
                    if (curr_room.state(laura.coord_x, laura.coord_y) == i + 9 or
                            curr_room.state(laura.coord_x + 30, laura.coord_y) == i + 9 or curr_room.state(
                                laura.coord_x, laura.coord_y + 50) == i + 9 or
                            curr_room.state(laura.coord_x + 30, laura.coord_y + 50) == i + 9):
                        wall = MAP[i]
                        matrix = wall.maze
                        graph = wall.block_maze
                        enemies = wall.enemies
                        steady_buff = wall.steady_buff
                        traps = wall.traps
                        curr_room = room.Room(matrix, laura_attack, list(), steady_buff,
                                              traps, enemies, list(), back_img, enemy_number, graph)
                        laura.position()

            # show_buff method
            # implement Props scattered in the chamber and their interaction with players
            def show_buff():
                for Obj in curr_room.steady_buff:
                    screen.blit(Obj.img, (Obj.coord_x, Obj.coord_y))
                    # if collide with Laura, affect Laura and remove this Prop
                    if pow(laura.coord_x - 10 - Obj.coord_x, 2) + pow(laura.coord_y - Obj.coord_y, 2) <= 1000:
                        # bullet supply(restore bullet)
                        if Obj.type == 0:
                            laura.remain_bullet += 20
                            sound_4 = pygame.mixer.Sound("sound/sound_effect/load_bullet.mp3")
                            sound_4.set_volume(0.5)
                            sound_4.play()
                        # food(restore HP)
                        elif Obj.type == 1:
                            laura.HP += 100
                            sound_5 = pygame.mixer.Sound("sound/sound_effect/eat.mp3")
                            sound_5.set_volume(1)
                            sound_5.play()
                            if laura.HP > laura.init_HP:
                                laura.HP = laura.init_HP
                        # speed boot and poison
                        elif Obj.type == 2:
                            laura.remain_buff += 2000
                            sound_6 = pygame.mixer.Sound("sound/sound_effect/buff.wav")
                            sound_6.set_volume(1)
                            sound_6.play()
                        elif Obj.type == 3:
                            sound_7 = pygame.mixer.Sound("sound/sound_effect/freeze.wav")
                            sound_7.set_volume(0.8)
                            sound_7.play()
                            laura.remain_de_buff += 2000
                        elif Obj.type == 4 and wall.enemy_number == 0:
                            sound_8 = pygame.mixer.Sound("sound/sound_effect/pick_bar.wav")
                            sound_8.set_volume(1)
                            sound_8.play()
                            laura.remain_bar += 1
                        # current Prop if it is triggered
                        if Obj.type != 4:
                            curr_room.steady_buff.remove(Obj)
                        elif Obj.type == 4 and wall.enemy_number == 0:
                            curr_room.steady_buff.remove(Obj)

            show_buff()

            # show_guard_star method
            # skeleton guard's spell, decrease Laura's speed
            # implement interaction between guard's spell and Laura & wall
            def show_guard_star():
                for s in curr_room.guard_bullet:
                    # show spell on the screen
                    screen.blit(s.image, (s.x, s.y))
                    s.x += s.star_speed * s.dir_x
                    s.y += s.star_speed * s.dir_y
                    # only cause effect if Laura is alive
                    if laura.alive:
                        if pow((laura.coord_x + 3 - s.x), 2) + pow((laura.coord_y + 13 - s.y), 2) <= 625:
                            sound_8 = pygame.mixer.Sound("sound/sound_effect/bullet_on_enemy.wav")
                            sound_8.set_volume(0.7)
                            sound_8.play()
                            laura.remain_de_buff += 400  # trigger frame-based timer
                            curr_room.guard_bullet.remove(s)
                            continue
                    if s.x < 20 or s.x > 610:
                        curr_room.guard_bullet.remove(s)
                        sound_10 = pygame.mixer.Sound("sound/sound_effect/bullet_on_wall.wav")
                        sound_10.set_volume(0.5)
                        sound_10.play()
                        continue
                    elif s.y < 20 or s.y > 610:
                        sound_11 = pygame.mixer.Sound("sound/sound_effect/bullet_on_wall.wav")
                        sound_11.set_volume(0.5)
                        sound_11.play()
                        curr_room.guard_bullet.remove(s)

                    if curr_room.state(s.x, s.y) == 1 or curr_room.state(s.x + 10, s.y) == 1 or \
                            curr_room.state(s.x, s.y + 14) == 1 or curr_room.state(
                            s.x + 10, s.y + 14) == 1:
                        curr_room.guard_bullet.remove(s)
                        sound_9 = pygame.mixer.Sound("sound/sound_effect/bullet_on_wall.wav")
                        sound_9.set_volume(0.5)
                        sound_9.play()
                        continue

            show_guard_star()

            # show_trap method
            # trap is visible or invisible before being triggered
            # trap causes effect and leaves wreckage
            def show_trap():
                for Obj in curr_room.traps:
                    screen.blit(Obj.image, (Obj.coord_x, Obj.coord_y))
                    if pow(laura.coord_x - Obj.coord_x, 2) + pow(laura.coord_y - Obj.coord_y, 2) <= 1000:
                        if Obj.type == 0:
                            if Obj.functioning():
                                laura.damage_player(trap_damage)
                                Obj.trigger()
                        elif Obj.type == 1:
                            if Obj.functioning():
                                laura.freeze_time += freeze_trap_time
                                Obj.trigger()

            show_trap()

            # show_enemy method
            # control the behaviors of mummies and skeleton guards
            # embedded A* algorithm
            # mummy and skeleton guard chase player in shortest path
            def show_enemy():
                for Emy in curr_room.enemies:
                    # ----------------tracking---------------
                    if laura.alive:
                        if Emy.state:
                            screen.blit(Emy.enemy_image_dead, (Emy.coord_x, Emy.coord_y))
                        if not Emy.state:
                            delta_x = laura.coord_x - Emy.coord_x
                            delta_y = laura.coord_y - Emy.coord_y

                            if Emy.kind != 3:
                                if delta_x < 0:
                                    screen.blit(Emy.enemy_image_left, (Emy.coord_x, Emy.coord_y))
                                else:
                                    screen.blit(Emy.enemy_image_right, (Emy.coord_x, Emy.coord_y))
                            else:
                                screen.blit(Emy.enemy_image, (Emy.coord_x, Emy.coord_y))

                            total_dis = pow(pow(delta_x, 2) + pow(delta_y, 2), 0.5)
                            if Emy.kind == 2:
                                if total_dis < 300 and Emy.remain_star != 0:
                                    if Emy.fire_time == 100:
                                        curr_star = EnemyStar.GuardStar(Emy, laura)
                                        curr_room.guard_bullet.append(curr_star)
                                        Emy.remain_star -= 1
                                        Emy.fire_time -= 1
                                    elif Emy.fire_time < 100:
                                        Emy.fire_time -= 1
                                    if Emy.fire_time == 0:
                                        Emy.fire_time = 100

                            if total_dis <= 600:
                                if Emy.kind == 3:
                                    pass
                                else:
                                    a_star_graph = list()
                                    for i in range(0, 26):
                                        row = list()
                                        a_star_graph.append(row)
                                        for j in range(0, 26):
                                            row.append(map.a_star_block(i, j))
                                    pa = find_path(curr_room.graph, a_star_graph, (Emy.coord_x, Emy.coord_y),
                                                   (laura.coord_x, laura.coord_y))
                                    Emy.path = tuple(get_path(a_star_graph, (Emy.coord_x, Emy.coord_y), pa))
                                    if len(Emy.path) >= 2:
                                        del_x = Emy.path[1][1] * 24 + 3 - Emy.coord_x
                                        del_y = Emy.path[1][0] * 24 + 3 - Emy.coord_y
                                    else:
                                        del_x = 0
                                        del_y = 0
                                    if del_x > 0:
                                        del_x = 1
                                    elif del_x < 0:
                                        del_x = -1
                                    if del_y > 0:
                                        del_y = 1
                                    elif del_y < 0:
                                        del_y = -1
                                    Emy.coord_x += Emy.enemy_speed * del_x
                                    Emy.coord_y += Emy.enemy_speed * del_y

                            if total_dis <= 64 and laura.is_flash:
                                if not Emy.state:
                                    Emy.damage_enemy(laura.melee_damage)
                                    Emy.death_()
                                    if Emy.state:
                                        wall.enemy_number -= 1
                                        curr_room.enemy_number -= 1

                            if total_dis <= 26 and not laura.is_flash:
                                if not Emy.state:
                                    if Emy.boom_time == 50:
                                        laura.damage_player(40)
                                        sound_11 = pygame.mixer.Sound("sound/sound_effect/enemy_attack.wav")
                                        sound_11.set_volume(1)
                                        sound_11.play()
                                        Emy.boom_time -= 1
                                    elif Emy.boom_time < 50:
                                        Emy.boom_time -= 1
                                    if Emy.boom_time == 0:
                                        Emy.boom_time = 50

            show_enemy()

            # summon method
            # control the behavior of the Pharaoh
            def summon():
                if wall.enemy_number < difficulty:
                    for curr_enemy in wall.enemies:
                        if curr_enemy.kind == 3:
                            if not curr_enemy.state:
                                if curr_enemy.fire_time == 500:
                                    kind, set_pos_x, set_pos_y = random.choice(([1, 483, 483], [2, 51, 483]))
                                    wall.enemies.append(enemy.Enemy(kind, set_pos_x, set_pos_y))
                                    curr_room.enemies = wall.enemies
                                    wall.increase_enemy(1)
                                    curr_room.enemy_number = wall.enemy_number
                                    curr_enemy.fire_time -= 1
                                    sound_13 = pygame.mixer.Sound("sound/sound_effect/summon.wav")
                                    sound_13.set_volume(0.8)
                                    sound_13.play()
                                elif curr_enemy.fire_time < 500:
                                    curr_enemy.fire_time -= 1
                                if curr_enemy.fire_time == 0:
                                    # reload timer
                                    curr_enemy.fire_time = 500

            summon()

            # show_enemy_hp method
            # draw HP bars at the head of enemies
            def show_enemy_hp():
                for Emy in curr_room.enemies:
                    # only generate HP bar for survive enemies
                    if Emy.state == False:
                        pygame.draw.rect(screen, (255, 0, 0),
                                         (Emy.coord_x, Emy.coord_y - 10, (Emy.HP / Emy.init_HP) * 60, 6))

            show_enemy_hp()

            # show shadow
            screen.blit(shadow, (laura.coord_x - 630, laura.coord_y - 600))

            # show_laura_hp method
            # generate HP bar for laura at a specific location of the screen
            def show_laura_hp():
                screen.blit(laura.player_head, (560, 630))
                # print text message
                my_font_hp = pygame.font.Font(None, 20)
                text_image_hp = my_font_hp.render("HP", True, pygame.Color(255, 255, 255))
                screen.blit(text_image_hp, (360, 620))
                pygame.draw.rect(screen, (255, 0, 0), (360, 640, (laura.HP / laura.init_HP) * 190, 15))

            show_laura_hp()

            # show_backpack method
            # generate backpack for Laura, show remaining things.
            def show_backpack():
                # backpack name
                my_font_bag = pygame.font.Font(None, 20)
                text_image_bag = my_font_bag.render("YOUR BAG", True, pygame.Color(255, 255, 255))
                screen.blit(text_image_bag, (0, 620))

                # load pack backgroud resource
                bag_bullets = pygame.image.load(r"image/bag_bullets.png")
                bag_bar = pygame.image.load(r"image/crowbar.png")
                for bag in bags:
                    screen.blit(bag.bag_image, (bag.coord_x, bag.coord_y))

                # load bullet icon
                screen.blit(bag_bullets, (32, 655))
                screen.blit(bag_bar, (88, 650))
                my_font_remain_bullets = pygame.font.Font(None, 20)
                text_image_remain_bullets = my_font_remain_bullets.render(str(laura.remain_bullet), True,
                                                                          pygame.Color(0, 0, 0))
                screen.blit(text_image_remain_bullets, (45, 640))

                my_font_remain_bars = pygame.font.Font(None, 20)
                text_image_remain_bars = my_font_remain_bars.render(str(laura.remain_bar), True,
                                                                    pygame.Color(0, 0, 0))
                screen.blit(text_image_remain_bars, (117, 640))

            show_backpack()

            # show_buff_icon method
            # generate laura's state bar, show if Laura is affected by time dependent effect(s)
            def show_buff_icon():
                if laura.freeze_time > 0:
                    screen.blit(freeze_icon, (470, 668))
                if laura.remain_de_buff > 0:
                    screen.blit(slow_icon, (500, 668))
                if laura.remain_buff > 0:
                    screen.blit(fast_icon, (530, 668))

            show_buff_icon()

            # mini-map implementation, not encapsulated
            frame = pygame.image.load(r"image/frame.png").convert_alpha()
            screen.blit(frame, (628, 0))

            map_word = pygame.image.load(r"image/map_word.png").convert_alpha()
            screen.blit(map_word, (628, 240))

            if wall == MAP[24]:
                special_room = pygame.image.load(r"image/special_room.png").convert_alpha()
                screen.blit(special_room, (628, 315))
                if laura.remain_bar == 0:
                    crowbar_small = pygame.image.load(r"image/crowbar_small.png").convert_alpha()
                    screen.blit(crowbar_small, (680, 365))
            elif wall == MAP[26] or wall == MAP[27]:
                special_room = pygame.image.load(r"image/special_room3.png").convert_alpha()
                screen.blit(special_room, (628, 315))
            else:
                special_room = pygame.image.load(r"image/special_room2.png").convert_alpha()
                screen.blit(special_room, (628, 315))

            boss_room = pygame.image.load(r"image/boss_room.png").convert_alpha()
            screen.blit(boss_room, (628, 510))

            for curr_enemy in wall.enemies:
                if curr_enemy.kind == 3:
                    if not curr_enemy.state:
                        enemy_3_small = pygame.image.load(r"image/enemy_3_small.png").convert_alpha()
                        screen.blit(enemy_3_small, (680, 560))
                    else:
                        enemy_3_small = pygame.image.load(r"image/enemy_3_small_dead.png").convert_alpha()
                        screen.blit(enemy_3_small, (680, 560))

            screen.blit(laura.little_head, (wall.small_x, wall.small_y))
            for each in Small_Map.blockGroup:
                screen.blit(each.image, each.rect)

            # print Laura image
            # including animation generation logic
            if laura.alive:
                if laura.is_flash:
                    laura.player_speed_x = 0
                    laura.player_speed_y = 0
                    if laura.flash_times > 0:
                        laura.flash_times -= 1
                        if 30 < laura.flash_times <= 40:
                            if laura.player_face == 0:
                                screen.blit(laura.player_attack1_le, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 1:
                                screen.blit(laura.player_attack1_ri, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 2:
                                screen.blit(laura.player_attack1_ri, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 3:
                                screen.blit(laura.player_attack1_le, (laura.coord_x, laura.coord_y))

                        elif 20 < laura.flash_times <= 30:
                            if laura.player_face == 0:
                                screen.blit(laura.player_attack2_le, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 1:
                                screen.blit(laura.player_attack2_ri, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 2:
                                screen.blit(laura.player_attack2_ri, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 3:
                                screen.blit(laura.player_attack2_le, (laura.coord_x, laura.coord_y))

                        elif 10 < laura.flash_times <= 20:
                            if laura.player_face == 0:
                                screen.blit(laura.player_attack3_le, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 1:
                                screen.blit(laura.player_attack3_ri, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 2:
                                screen.blit(laura.player_attack3_ri, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 3:
                                screen.blit(laura.player_attack3_le, (laura.coord_x, laura.coord_y))

                        elif 0 < laura.flash_times <= 10:
                            if laura.player_face == 0:
                                screen.blit(laura.player_attack4_le, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 1:
                                screen.blit(laura.player_attack4_ri, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 2:
                                screen.blit(laura.player_attack4_ri, (laura.coord_x, laura.coord_y))
                            if laura.player_face == 3:
                                screen.blit(laura.player_attack4_le, (laura.coord_x, laura.coord_y))
                    else:
                        laura.flash_times = 40
                        laura.is_flash = False

                else:
                    if laura.is_walk:
                        if laura.walk_flash_times > 0:
                            laura.walk_flash_times -= 1
                            if 30 <= laura.walk_flash_times <= 40:
                                if laura.player_face == 0:
                                    screen.blit(laura.player_up_2, (laura.coord_x, laura.coord_y))
                                elif laura.player_face == 1:
                                    screen.blit(laura.player_down_2, (laura.coord_x, laura.coord_y))
                                elif laura.player_face == 2:
                                    screen.blit(laura.player_right_4, (laura.coord_x, laura.coord_y))
                                else:
                                    screen.blit(laura.player_left_4, (laura.coord_x, laura.coord_y))

                            if 20 <= laura.walk_flash_times <= 30:
                                if laura.player_face == 0:
                                    screen.blit(laura.player_up_1, (laura.coord_x, laura.coord_y))
                                elif laura.player_face == 1:
                                    screen.blit(laura.player_down_1, (laura.coord_x, laura.coord_y))
                                elif laura.player_face == 2:
                                    screen.blit(laura.player_right_3, (laura.coord_x, laura.coord_y))
                                else:
                                    screen.blit(laura.player_left_3, (laura.coord_x, laura.coord_y))

                            if 10 <= laura.walk_flash_times <= 20:
                                if laura.player_face == 0:
                                    screen.blit(laura.player_up_2, (laura.coord_x, laura.coord_y))
                                elif laura.player_face == 1:
                                    screen.blit(laura.player_down_2, (laura.coord_x, laura.coord_y))
                                elif laura.player_face == 2:
                                    screen.blit(laura.player_right_2, (laura.coord_x, laura.coord_y))
                                else:
                                    screen.blit(laura.player_left_2, (laura.coord_x, laura.coord_y))

                            if 0 <= laura.walk_flash_times <= 10:
                                if laura.player_face == 0:
                                    screen.blit(laura.player_up_1, (laura.coord_x, laura.coord_y))
                                elif laura.player_face == 1:
                                    screen.blit(laura.player_down_1, (laura.coord_x, laura.coord_y))
                                elif laura.player_face == 2:
                                    screen.blit(laura.player_right_1, (laura.coord_x, laura.coord_y))
                                else:
                                    screen.blit(laura.player_left_1, (laura.coord_x, laura.coord_y))
                        else:
                            laura.walk_flash_times = 40
                            if laura.player_face == 0:
                                screen.blit(laura.player_up_2, (laura.coord_x, laura.coord_y))
                            elif laura.player_face == 1:
                                screen.blit(laura.player_down_2, (laura.coord_x, laura.coord_y))
                            elif laura.player_face == 2:
                                screen.blit(laura.player_right_4, (laura.coord_x, laura.coord_y))
                            else:
                                screen.blit(laura.player_left_4, (laura.coord_x, laura.coord_y))
                    else:
                        if laura.player_face == 0:
                            screen.blit(laura.player_up_1, (laura.coord_x, laura.coord_y))
                        if laura.player_face == 1:
                            screen.blit(laura.player_down_1, (laura.coord_x, laura.coord_y))
                        if laura.player_face == 2:
                            screen.blit(laura.player_right_1, (laura.coord_x, laura.coord_y))
                        if laura.player_face == 3:
                            screen.blit(laura.player_left_1, (laura.coord_x, laura.coord_y))
            else:
                screen.blit(laura.player_death, (laura.coord_x, laura.coord_y))

            pygame.display.update() # update current frame

    show_map()


if __name__ == '__main__':
    main()
